package com.youku.bootstrap;

import static com.youku.util.XMLHelper.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.youku.component.BaseAttribute;
import com.youku.component.Project;
import com.youku.component.ProjectHolder;
import com.youku.component.SleepTimer;
import com.youku.component.Step;
import com.youku.component.Template;
import com.youku.component.TestCase;
import com.youku.component.TestSuite;
import com.youku.component.Validation;
import com.youku.component.connection.ConnectorFactory;
import com.youku.component.notification.NotificationController;
import com.youku.component.notification.Recipient;
import com.youku.component.procedure.HttpProcedure;
import com.youku.component.procedure.MysqlProcedure;
import com.youku.component.variable.GlobalVariable;
import com.youku.component.variable.LocalDefVariable;
import com.youku.component.variable.LocalNormalVariable;
import com.youku.exception.NotRecognizedTagException;
import com.youku.exception.RefErrorElementException;
import com.youku.expression.Position;
import com.youku.util.BooleanHelper;

public class Scheduler {

	private SAXReader reader;

	/**
	 * 可以存储多个项目文档
	 */
	private List<Document> coreDocuments;

	/**
	 * 登记在册，可以被通知的项目
	 */
	private Set<String> toNotify;

	/**
	 * 存储多个通知配置文件
	 */
	private List<Document> notifyDocuments;

	/**
	 * 定时执行器
	 */
	private PlatformExecutor executor;

	/**
	 * 使用单例模式保护
	 */
	private Scheduler(String command) {
		reader = new SAXReader();
		executor = PlatformExecutorHolder.getExecutor(command);
		coreDocuments = new LinkedList<>();
		notifyDocuments = new LinkedList<>();
		toNotify = new HashSet<>();
	}

	public static final Scheduler getInstance(String command) {
		return new Scheduler(command);
	}

	/**
	 * 构建测试用例配置文件
	 * 
	 * @param file
	 *            文件
	 */
	public void buildCoreDocument(String name) {
		coreDocuments.add(readDocument(name));
	}

	/**
	 * 构建通知配置文件
	 * 
	 * @param file
	 *            文件
	 */
	public void buildNotifyDocument(String name) {
		notifyDocuments.add(readDocument(name));
	}

	private Document readDocument(String fileName) {
		try {
			File file = new File(fileName);
			return reader.read(new File(file.getCanonicalPath()));
		} catch (Exception e) {
			throw new IllegalArgumentException(e);
		}
	}

	public void schedule() {
		// 检查配置文件
		assertMissingConfig();

		// 分析通知配置文件
		analysisNotifyDocument();

		// 分析测试用例配置文件
		analysisCoreDocument();

		// 执行平台的测试任务
		executePlatform();
	}

	private void executePlatform() {
		executor.execute();
	}

	private void analysisNotifyDocument() {
		// 多个配置文档
		for (Document document : notifyDocuments) {
			Element root = document.getRootElement();

			Element element = elementNotNull(root, "project");
			
			// 项目id
			String pId = valueNotNull(element, "id");

			List<Recipient> recipients = new LinkedList<>();

			for (Element rpt : elements(elementNotNull(element, "recipients"), "recipient")) {
				recipients.add(new Recipient(valueNotNull(rpt, "email"), valueNotNull(rpt, "cellNumber")));
			}

			toNotify.add(pId);
			
			// 绑定通知信息
			NotificationController.addNotificationConfig(pId, elementTextTrim(element, "display"), recipients);
		}

	}

	private void analysisCoreDocument() {
		// 多个项目
		for (Document document : coreDocuments) {
			Element root = document.getRootElement();

			// 获取并绑定项目信息
			Project project = buildProject(elementNotNull(root, "project"));
			ProjectHolder.binding(project.getId(), project);

			// 将获取的项目信息填充到定时执行器中
			executor.addExecutor(project);
		}
	}

	private void assertMissingConfig() {
		assertFalse("测试用例配置文件不存在", coreDocuments.isEmpty());
		assertNotNull("通知配置文件不存在", notifyDocuments.isEmpty());
	}

	private Project buildProject(Element element) {

		Project p = new Project();

		p.setId(valueNotNull(element, "id"));

		assertTrue("通知配置文件的项目id和测试用例配置文件的项目id不匹配", isProjectToNotify(p.getId()));

		p.setSetup(value(element, "setup"));

		p.setTeardown(value(element, "teardown"));

		p.setCronExpression(value(element, "cronExpression"));

		for (Element projectElement : elements(element)) {
			switch (projectElement.getName()) {
			case "global":
				buildGlobal(projectElement, p);
				break;
			case "template":
				buildTemplate(projectElement, p);
				break;
			case "module":
				buildModule(projectElement, p);
				break;
			case "conn":
				buildConnection(projectElement, p);
				break;
			case "sleep":
				buildSleep(projectElement, p);
				break;
			default:
				throw new NotRecognizedTagException(
						"无法识别" + projectElement.getName() + "标签。这个范围内只能使用global,template,module,conn,sleep这些标签");
			}
		}

		return p;
	}

	private void buildModule(Element element, Project p) {

		Document document = readDocument(valueNotNull(element, "location"));

		for (Element suiteElement : elements(document.getRootElement())) {
			switch (suiteElement.getName()) {
			case "refsuite":
				// 引用测试用例组
				buildRefSuite(suiteElement, p);
				break;
			case "suite":
				// 建立新的测试用例组
				buildSuite(suiteElement, p);
				break;
			case "sleep":
				// 构建睡眠
				buildSleep(suiteElement, p);
				break;
			default:
				throw new NotRecognizedTagException(
						"无法识别" + suiteElement.getName() + "标签。这个范围内只能使用refsuite,testsuite,sleep这些标签");
			}
		}

	}

	private void buildSuite(Element element, Project p) {
		TestSuite suite = new TestSuite(p);
		suite.setId(valueNotNull(element, "id"));
		suite.setSetup(value(element, "setup"));
		suite.setTeardown(value(element, "teardown"));

		// 建立测试用例
		buildCases(elements(element), suite);

		p.addStep(suite.getId(), suite);
	}

	private void buildCases(List<Element> elements, TestSuite suite) {
		// 测试用例
		for (Element element : elements) {
			switch (element.getName()) {
			case "refcase":
				// 引用测试用例
				buildRefCase(element, suite);
				break;
			case "case":
				// 构建测试用例
				buildCase(element, suite);
				break;
			case "sleep":
				// 构建睡眠
				buildSleep(element, suite);
				break;
			default:
				throw new NotRecognizedTagException(
						"无法识别" + element.getName() + "标签。这个范围内只能使用refcase,reftemp,testcase,sleep这些标签");
			}
		}

	}

	private void buildRefTemp(Element element, TestCase testCase) {
		String refId = valueNotNull(element, "ref");

		Template template = testCase.getProject().getTemplate(refId);

		Map<String, String> params = getParamsFromRefTemp(element);

		testCase.refTemplate(template, params);

	}

	private Map<String, String> getParamsFromRefTemp(Element element) {
		Map<String, String> map = new HashMap<>();
		for (Element e : elements(element, "param")) {
			map.put(valueNotNull(e, "name"), valueNotNullExt(e, "value"));
		}
		return map;
	}

	private void buildRefCase(Element element, TestSuite suite) {
		String refId = valueNotNull(element, "ref");

		String newId = valueNotNull(element, "id");

		Step step = suite.getStepWithKey(refId);

		if (step instanceof TestCase) {
			TestCase cs = (TestCase) step;
			cs.setId(newId);
			// 是否调用初始化和结束方法，默认为是
			cs.invokeSetup(BooleanHelper.toBooleanDefaultTrue(value(element, "init")));
			cs.invokeTeardown(BooleanHelper.toBooleanDefaultTrue(value(element, "destroy")));

			suite.addStep(cs.getId(), cs);
		} else {
			throw new RefErrorElementException("引用的元素不合法");
		}
	}

	private void initTestCase(Element element, TestCase testCase) {
		testCase.setId(valueNotNull(element, "id"));
		testCase.setSetup(value(element, "setup"));
		testCase.setTeardown(value(element, "teardown"));

		for (Element e : elements(element)) {
			switch (e.getName()) {
			case "def":
				buildDef(e, testCase);
				break;
			case "local":
				buildLocal(e, testCase);
				break;
			case "reftemp":
				// 引用测试模板
				buildRefTemp(e, testCase);
				break;
			case "global":
				buildGlobal(e, testCase.getProject());
				break;
			case "http":
				buildHttpProcedure(e, testCase);
				break;
			case "mysql":
				buildMysqlProcedure(e, testCase);
				break;
			case "valid":
				buildValidation(e, testCase);
				break;
			case "sleep":
				buildSleep(e, testCase);
				break;
			default:
				throw new NotRecognizedTagException(
						"无法识别" + e.getName() + "标签。这个范围内只能使用def,param,local,global,http,mysql,valid,sleep这些标签");
			}
		}
	}

	private void buildTemplate(Element element, Project p) {
		Template template = new Template(p);
		initTestCase(element, template);
		p.addTemplate(template.getId(), template);
	}

	private void buildCase(Element element, TestSuite suite) {
		TestCase testCase = new TestCase(suite);
		initTestCase(element, testCase);
		suite.addStep(testCase.getId(), testCase);

	}

	private void buildValidation(Element element, TestCase testCase) {
		testCase.addStep(null, new Validation(Position.create(testCase), element.getTextTrim()));
	}

	private void buildGlobal(Element element, Project project) {
		project.addStep(null,
				new GlobalVariable(valueNotNull(element, "name"), valueNotNullExt(element, "value"), project));
	}

	private void buildDef(Element element, TestCase testCase) {
		testCase.addStep(null,
				new LocalDefVariable(valueNotNull(element, "name"), value(element, "default"), testCase));
	}

	private void buildLocal(Element element, TestCase testCase) {
		testCase.addStep(null,
				new LocalNormalVariable(valueNotNull(element, "name"), valueNotNullExt(element, "value"), testCase));
	}

	private void buildMysqlProcedure(Element element, TestCase testCase) {
		MysqlProcedure procedure = new MysqlProcedure(valueNotNull(element, "id"), valueNotNull(element, "connect"),
				testCase, value(element, "times"));

		StringBuilder builder = new StringBuilder();

		for (Element e : elements(element, "sql")) {
			builder.append(e.getTextTrim()).append(';');
		}

		procedure.setSql(builder.toString());

		testCase.addStep(procedure.getKey(), procedure);
	}

	private void buildHttpProcedure(Element element, TestCase testCase) {
		HttpProcedure procedure = new HttpProcedure(valueNotNull(element, "id"), testCase, value(element, "times"));

		procedure.setUrl(valueNotNullExt(element, "url"));

		Element body = element(element, "body");
		if (body != null)
			procedure.setBody(body.getTextTrim());

		for (Element argument : elements(element, "arg")) {
			procedure.addArgument(valueNotNull(argument, "name"), valueNotNullExt(argument, "value"));
		}

		for (Element head : elements(element, "head")) {
			procedure.addHead(valueNotNull(head, "name"), valueNotNullExt(head, "value"));
		}

		for (Element cookie : elements(element, "cookie")) {
			procedure.addCookie(valueNotNull(cookie, "name"), valueNotNullExt(cookie, "value"));
		}

		testCase.addStep(procedure.getKey(), procedure);
	}

	private void buildSleep(Element element, BaseAttribute attribute) {
		attribute.addStep(null, new SleepTimer(Position.create(attribute), valueNotNull(element, "time")));
	}

	private void buildRefSuite(Element element, Project p) {
		String refId = valueNotNull(element, "ref");

		String newId = valueNotNull(element, "id");

		Step step = p.getStepWithKey(refId);

		if (step instanceof TestSuite) {
			TestSuite suite = (TestSuite) step;
			suite.setId(newId);
			// 是否调用初始化和结束方法，默认为是
			suite.invokeSetup(BooleanHelper.toBooleanDefaultTrue(value(element, "init")));
			suite.invokeTeardown(BooleanHelper.toBooleanDefaultTrue(value(element, "destroy")));

			p.addStep(p.getId(), suite);
		} else {
			throw new RefErrorElementException("引用的元素不合法");
		}
	}

	private void buildConnection(Element element, Project p) {
		Map<String, String> map = new HashMap<>();
		for (Element e : elements(element, "param")) {
			map.put(valueNotNull(e, "name"), valueNotNullExt(e, "value"));
		}

		p.addConnector(valueNotNull(element, "id"), ConnectorFactory.build(map));
	}

	private boolean isProjectToNotify(String id) {
		return toNotify.contains(id);
	}

}
